package ru.classbase.dbdumper.writer

import org.springframework.stereotype.Component
import ru.classbase.dbdumper.AppProperties
import ru.classbase.dbdumper.model.Column
import ru.classbase.dbdumper.model.DbMetaData
import ru.classbase.dbdumper.model.DumpContext
import ru.classbase.dbdumper.model.OutputFormat
import java.io.Writer
import java.sql.ResultSet
import java.time.format.DateTimeFormatter

@Component
class PostgresSqlInsertWriter(
    private val props: AppProperties,
    private val dateTimeFormatter: DateTimeFormatter,
    private val dateFormatter: DateTimeFormatter,
) : DumpWriter {

    override val supportedFormat =  OutputFormat.POSTGRES_SQL_INSERT

    override fun write(metaData: DbMetaData, context: DumpContext) {
        val (schema, tableName, columns, rs, writer) = context

        val joined = columns.joinToString(", ") { it.columnName }

        val insert = "insert into $schema.$tableName ($joined) values\n"

        var valuesCount = 0

        while (rs.next()) {
            if (valuesCount % props.rowsPerInsert == 0) {
                writer.write(insert)
                valuesCount = 0
            }
            valuesCount++

            writeValues(rs, writer, columns)

            if (valuesCount % props.rowsPerInsert == 0) {
                writer.write(";\n")
            }

            writer.append('\n')
        }

    }

    private fun writeValues(rs: ResultSet, writer: Writer, columns: List<Column>) {
        writer.append('(')

        for (i in 1..columns.size) {
            when (columns[i - 1].dataType) {
                "integer" -> writeInt(writer, rs, i)
                "date" -> writeDate(writer, rs, i)
                "smallint" -> writeSmallInt(writer, rs, i)
                "timestamp without time zone" -> writeTimestamp(writer, rs, i)
                "boolean" -> writeBoolean(writer, rs, i)
                "character varying" -> writeString(writer, rs, i)
                "numeric" -> writeNumeric(writer, rs, i)
                "bigint" -> writeBigInt(writer, rs, i)
                else -> throw IllegalArgumentException("Тип данных [${columns[i - 1].dataType}]  не поддерживается")
            }

            if (i < columns.size) {
                writer.write(", ")
            }
        }

        writer.append(')')
    }

    fun writeString(writer: Writer, rs: ResultSet, colNum: Int) {
        val value = rs.getString(colNum)

        if (rs.wasNull()) {
            writer.write("null")
            return
        }

        writer.append('\'')
        writer.write(value)
        writer.append('\'')
    }

    fun writeBoolean(writer: Writer, rs: ResultSet, colNum: Int) {
        val value = rs.getBoolean(colNum)

        if (rs.wasNull()) {
            writer.write("null")
            return
        }

        if (value) {
            writer.write("true")
        } else {
            writer.write("false")
        }
    }


    fun writeSmallInt(writer: Writer, rs: ResultSet, colNum: Int) {
        val value = rs.getShort(colNum)

        if (rs.wasNull()) {
            writer.write("null")
            return
        }

        writer.write(value.toString())
    }

    fun writeInt(writer: Writer, rs: ResultSet, colNum: Int) {
        val value = rs.getInt(colNum)

        if (rs.wasNull()) {
            writer.write("null")
            return
        }

        writer.write(value.toString())

    }

    fun writeBigInt(writer: Writer, rs: ResultSet, colNum: Int) {
        val value = rs.getLong(colNum)

        if (rs.wasNull()) {
            writer.write("null")
            return
        }

        writer.write(value.toString())
    }

    fun writeNumeric(writer: Writer, rs: ResultSet, colNum: Int) {
        val value = rs.getBigDecimal(colNum)

        if (rs.wasNull()) {
            writer.write("null")
            return
        }
        writer.write(value.toPlainString())
    }

    fun writeDate(writer: Writer, rs: ResultSet, colNum: Int) {
        val value = rs.getDate(colNum)

        if (rs.wasNull()) {
            writer.write("null")
            return
        }

        writer.write(dateFormatter.format(value.toLocalDate()))
    }

    fun writeTimestamp(writer: Writer, rs: ResultSet, colNum: Int) {
        val value = rs.getTimestamp(colNum)

        if (rs.wasNull()) {
            writer.write("null")
            return
        }

        writer.write(dateTimeFormatter.format(value.toLocalDateTime()))
    }

}